﻿#region License
// Copyright (c) 2007 James Newton-King
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Globalization;
using Newtonsoft.Utilities.Text;
using Newtonsoft.Utilities.Miscellaneous;

namespace Newtonsoft.Utilities.Logging
{
  public static class Logger
  {
    public static IList<ILogger> Loggers { get; private set; }

    static Logger()
    {
      Loggers = new List<ILogger>();
    }

    public static void Trace(string operation, Action a)
    {
      Trace(operation, true, a);
    }

    public static void Trace(string operation, bool indent, Action a)
    {
      if (a == null)
        throw new ArgumentNullException("a");

      Func<object> f = () => { a(); return null; };

      Trace(operation, f);
    }

    public static T Trace<T>(string operation, Func<T> f)
    {
      return Trace(operation, true, f);
    }

    public static T Trace<T>(string operation, bool indent, Func<T> f)
    {
      if (f == null)
        throw new ArgumentNullException("f");

      if (indent)
        Indent();

      System.Diagnostics.Trace.CorrelationManager.StartLogicalOperation(operation);

      Log("Trace Start: " + operation);

      Stopwatch stopwatch = new Stopwatch();

      try
      {
        stopwatch.Start();
        return f();
      }
      catch (Exception ex)
      {
        LogException(ex, "Trace Error:", operation);

        throw;
      }
      finally
      {
        System.Diagnostics.Trace.CorrelationManager.StopLogicalOperation();
        Log("Trace End: {0} (elapsed time: {1} seconds)".FormatWith(CultureInfo.InvariantCulture, operation, GetSecondsElapsed(stopwatch.ElapsedMilliseconds)));

        if (indent)
          Unindent();
      }
    }

    public static void LogException(Exception exception, string message, string category)
    {
      StringWriter sw = new StringWriter(CultureInfo.InvariantCulture);
      TextExceptionFormatter exceptionFormatter = new TextExceptionFormatter(sw, exception);
      exceptionFormatter.Format();

      string exceptionDetails = sw.ToString();
      exceptionDetails = exceptionDetails.Indent(IndentSize);

      LogExceptionInternal(message + Environment.NewLine + exceptionDetails, category);
    }

    public static void LogException(Exception exception, string category)
    {
      StringWriter sw = new StringWriter(CultureInfo.InvariantCulture);
      TextExceptionFormatter exceptionFormatter = new TextExceptionFormatter(sw, exception);
      exceptionFormatter.Format();

      string exceptionDetails = sw.ToString();

      LogExceptionInternal(exceptionDetails, category);
    }

    private static void LogExceptionInternal(string exceptionDetails, string category)
    {
      Log(exceptionDetails, category, 5, 1, TraceEventType.Error, "Error", null);
    }

    private static decimal GetSecondsElapsed(long milliseconds)
    {
      decimal d = Convert.ToDecimal(milliseconds) / 1000M;
      return Math.Round(d, 6);
    }

    public static void Indent()
    {
      IndentLevel++;
    }

    public static void Unindent()
    {
      IndentLevel--;
    }

    public static int IndentLevel
    {
      get { return System.Diagnostics.Trace.IndentLevel; }
      set { System.Diagnostics.Trace.IndentLevel = value; }
    }

    public static int IndentSize
    {
      get { return System.Diagnostics.Trace.IndentSize; }
      set { System.Diagnostics.Trace.IndentSize = value; }
    }

    public static void Log(string message)
    {
      LogInternal(new LogEntry { Message = message });
    }

    public static void Log(string message, string category)
    {
      LogInternal(new LogEntry { Message = message, Categories = { category } });
    }

    public static void Log(string message, string category, TraceEventType severity)
    {
      LogInternal(new LogEntry { Message = message, Categories = { category }, Severity = severity });
    }

    public static void Log(string message, string category, int priority, int eventId, TraceEventType severity, string title, IDictionary<string, object> properties)
    {
      LogInternal(new LogEntry { Message = message, Categories = { category }, Priority = priority, Severity = severity, EventId = eventId, Properties = properties });
    }

    public static void Log(string message, ICollection<string> categories)
    {
      LogInternal(new LogEntry { Message = message, Categories = categories });
    }

    public static void Log(string message, ICollection<string> categories, TraceEventType severity)
    {
      LogInternal(new LogEntry { Message = message, Categories = categories, Severity = severity });
    }

    public static void Log(LogEntry entry)
    {
      ValidationUtils.ArgumentNotNull(entry, "entry");

      LogInternal(entry);
    }

    private static void LogInternal(LogEntry entry)
    {
      if (IndentSize > 0 && IndentLevel > 0)
        entry.Message = entry.Message.Indent(IndentLevel * IndentSize);

      foreach (ILogger logger in Loggers)
      {
        logger.Log(entry);
      }
    }
  }
}